crackmeUK 解析手引き その3(NORMAL)
それでは、crackme_UK 、NORMAL の解説に入りたいと思います。 OllyDBGを起動して、CrackMe UK を読み込んで下さい。 そのまま F9 キーで実行しましょう。CrackMe UK のウィンドウが表示されたかと思います。 EASY の正解パスの形式に倣い "CR0123-4567-89AB" と入力して、 「検索→現在のモジュール名」から GetWindowTextA ( Address 0041C430 USER32.GetWindowTextA) にブレークポイントを仕掛けてから OK ボタンを押すと 00417135 でブレークします。 0041712A |. FF7424 08 push dword ptr ss:[esp+8] ; /Count 0041712E |. FF7424 08 push dword ptr ss:[esp+8] ; |Buffer 00417132 |. FF71 1C push dword ptr ds:[ecx+1C] ; |hWnd 00417135 |. FF15 30C44100 call near dword ptr ds:[<&USER32.GetWindowTextA> ; \GetWindowTextA F8 キーを3回押して関数を抜けましょう。 00401EA0 |. BF D0594200 mov edi, CRACKME.004259D0 ; ASCII "9876543210" ここに出るはずです。そのすぐ下にこんな命令があります。 00401EA5 |. 83C9 FF or ecx, FFFFFFFF ; ecx 初期化 00401EA8 |. 33C0 xor eax, eax ; eax 初期化 00401EAA |. F2:AE repne scas byte ptr es:[edi] 00401EAC |. F7D1 not ecx 00401EAE |. 49 dec ecx 00401EAF |. 83F9 10 cmp ecx, 10 00401EB2 |. 0F85 DD000000 jnz CRACKME.00401F95 edi レジスタには入力文字列のポインタが入っていますが、 repne はストリングス命令と呼ばれるものの一つで、 eax レジスタとの比較結果が等しくないか ecx ≠ 0 であれば同じ命令を繰り返し実行します。 今回は xor eax, eax 命令でゼロ初期化、repne 命令ではバイト型でキャストされているので al (= 0) と入力文字を 先頭から順に比較、すなわちnull 文字が見つかるまで繰り返されます。 その際 edi レジスタの値は自動的にインクリメントされます。 さらに ecx レジスタ命令もその都度デクリメントされますが、ecx レジスタの値に注目です。 repne 命令を抜けると、ecx=FFFFFFEE となりましたが、これは -18 を意味しています。 正直この部分はさして重要というわけでもないのですが、ストリングス命令があるということで 解説いってみましょう。ただ、その前に2の補数について簡単に説明したいとおもいます。 多くのコンピュータでは負数を「2の補数」というもので表します。 ご存じの通り、符号ビットは一番左にあるわけですが、 例えば 7 を2進数(8ビット)表記で表すと、 00000111 、-7 は 11111001 です。 00000111(7) はわかると思いますが、符号ビットは一番左にあるのに なぜ -7 は 10000111 ではなく 11111001 なのでしょうか。 これが2の補数の特徴なのですが、その出し方は 1) まず +7 の2進数を求めます(8ビット表記)。 .. 00000111 2) ビットを反転(not)する。これは1の補数である。 .. 11111000 3) 1の補数に1を加えると2の補数になる。 .. 11111001 しかし、なぜこんな一見面倒なことをやるのでしょうか。それは、 負数を2の補数で表すと、減算を加算で表現できるからです。 00001010 <- 10 +) 11111001 <- -7 ----------- 100000011 溢れたビットを潰すと 00000011 <- 3 となるわけです。 さて、実際のコードですが、 00401EA0 |. BF D0594200 mov edi, CRACKME.004259D0 ; ASCII "9876543210" 00401EA5 |. 83C9 FF or ecx, FFFFFFFF ; ecx 初期化(FFFFFFFFh = -1) 00401EA8 |. 33C0 xor eax, eax ; eax 初期化 00401EAA |. F2:AE repne scas byte ptr es:[edi] 00401EAC |. F7D1 not ecx 00401EAE |. 49 dec ecx 00401EAF |. 83F9 10 cmp ecx, 10 ; ecx には文字数が入る 00401EB2 |. 0F85 DD000000 jnz CRACKME.00401F95 の 00401EAC |. F7D1 not ecx 00401EAE |. 49 dec ecx ; inc eax ではない! この部分が該当するのですが、 inc ecx ではなくて dec ecx となっています。 実はこれには理由があります。 まずアドレス 00401EA0 にブレークポイントを仕掛けましょう。 その後、パスワードを入れずに OK ボタンを押してみてください。ブレークしましたね。 00401EA5 |. 83C9 FF or ecx, FFFFFFFF ; ecx 初期化(FFFFFFFFh = -1) まず、ecx レジスタに -1 が入ります。なぜ 0 では無いのか? ストリングス命令において ecx レジスタはカウンタの役割を果たしており、 ecx = 0 になるとループが終了してしまうからです。 00401EA8 |. 33C0 xor eax, eax ; eax 初期化 これは文字列(というよりも連続したデータ)と比較したい値をセットしています。 ここでは 0 ですね。 00401EAA |. F2:AE repne scas byte ptr es:[edi] ストリングス命令です。edi レジスタには文字列のポインタが入っています。 ここでは繰り返し実行はせずにすぐ抜けるのですが、ecx は一度デクリメントされ ecx = FFFFFFFE(-2) となります。 00401EAC |. F7D1 not ecx 00401EAE |. 49 dec ecx そして ecx レジスタの値に対し、2の補数をとっているような気がしますが、 00401EAE |. 49 dec ecx inc eax ではありません。もし純粋に2の補数を取るのであれば、デクリメント命令を 実行する前の ecx レジスタの値は ecx = 0 でないといけないはずです。 しかし、デクリメント命令実行前の ecx レジスタの値は FFFFFFFE(-2) です。 or ecx, FFFFFFFF repne scas byte ptr es:[edi] この2つの命令で実際の文字数と比べて2少なくなっています。この値のズレを補正するために ~ecx + 1 - 2 = ~ecx - 1 ~~~ ~~~ としているわけです。従って dec ecx になるというわけです。 お分かり頂けたでしょうか? 単に文字列を取得したいのであれば、lstrlen でもいけるのですが、 asm 的に処理したいのであれば、こういう方法があるのですね。 VC がこんなコードを出力するということに感心しました(笑) さて、 ecx レジスタには入力文字列の文字数が入ったわけですが、cmp ecx, 10 で、 入力パスが 16 文字であるかチェックしています。今回は入力パスが 16 文字なので問題ありません。 次に、 00401EB8 |. 6A 02 push 2 00401EBA |. 68 58224200 push CRACKME.00422258 ; ASCII "CR" 00401EBF |. 68 D0594200 push CRACKME.004259D0 ; ASCII "CR0123-4567-89AB" 00401EC4 |. E8 C72D0000 call CRACKME.00404C90 ; 怪しい関数 00401EC9 |. 83C4 0C add esp, 0C 00401ECC |. 85C0 test eax, eax 00401ECE |. 0F85 C1000000 jnz CRACKME.00401F95 と、ありますが、引数の形で関数がやっていることはほぼわかりますね。 先頭2文字が "CR" であるかチェックしています。次にいきましょう。 00401ED4 |. 8A0D D6594200 mov cl, byte ptr ds:[4259D6] ; [004259D6] = 入力パス 7 文字目 00401EDA |. B0 2D mov al, 2D ; 2Dh = '-' 00401EDC |. 3AC8 cmp cl, al 00401EDE |. 0F85 B1000000 jnz CRACKME.00401F95 00401EE4 |. 3805 DB594200 cmp byte ptr ds:[4259DB], al ; [004259DB] = 入力パス 12 文字目 00401EEA |. 0F85 A5000000 jnz CRACKME.00401F95 ここでは入力パスの 7 文字目、12 文字目がハイフンであるかどうかをチェックしています。 この点については、ダンプウィンドウにフォーカスを合わせ Ctrl + G でアドレス 004259D6 に 移動してみるとよくわかると思います。 これより、入力パスの形態は "CRxxxx-xxxx-xxxx" のような形であることがわかりました。 EASY とは違い、ハイフンの位置も決められているようです。 さて、次に行きます。 00401EF0 |. BE D2594200 mov esi, CRACKME.004259D2 ; ASCII "0123-4567-89AB" 00401EF5 |> 56 /push esi ; CRACKME.004259D2 00401EF6 |. E8 35FEFFFF |call CRACKME.00401D30 00401EFB |. 83C4 04 |add esp, 4 00401EFE |. 85C0 |test eax, eax 00401F00 |. 0F84 8F000000 |je CRACKME.00401F95 00401F06 |. 83C6 05 |add esi, 5 00401F09 |. 81FE E1594200 |cmp esi, CRACKME.004259E1 00401F0F |.^7C E4 \jl short CRACKME.00401EF5 00401F11 |. 8B83 D0000000 mov eax, dword ptr ds:[ebx+D0] このループはなにをやっているのでしょうか。少々見当がつかないと思いますが、 ダンプウィンドウに表示されている入力パスの文字列を眺めつつトレースするとよくわかります。 004259D0 43 52 30 31 32 33 2D 34 35 36 37 2D 38 39 41 42 CR0123-4567-89AB ; ループ前 004259D0 43 52 00 01 02 03 2D 04 05 06 07 2D 08 09 0A 0B CR....-....-.... ; ループ後 このループは、文字としての値を16進数の値に変更しているようです。 関数の最後の方で "Congraturations!!!" という文字列が参照されていますが、 ここから先のキーチェックルーチンが見つからず苦労された方が多いと思います。 この先で呼び出されている API を確認してみましょう。 00401F47 |. FF15 64C14100 call near dword ptr ds:[<&KERNEL32.ResumeThread> ; \ResumeThread 00401F53 |. FF15 68C14100 call near dword ptr ds:[<&KERNEL32.WaitForSingleObject> ; \WaitForSingleObject 00401F62 |. FF15 6CC14100 call near dword ptr ds:[<&KERNEL32.GetExitCodeThread> ; \GetExitCodeThread これらの API からキーチェック自体は別スレッドにて行っているようです。 00401F17 |. 6A 00 push 0 00401F19 |. 6A 04 push 4 00401F1B |. 6A 00 push 0 00401F1D |. 8D4C24 1C lea ecx, dword ptr ss:[esp+1C] 00401F21 |. 6A 00 push 0 00401F23 |. 51 push ecx 00401F24 |. 68 701D4000 push CRACKME.00401D70 ; ? 00401F29 |. C74424 28 D059>mov dword ptr ss:[esp+28], CRACKME.004259D0 ; ASCII "CR" 00401F31 |. 894424 2C mov dword ptr ss:[esp+2C], eax 00401F35 |. E8 7D0A0100 call CRACKME.004129B7 <-- おそらくこの call で別スレッドを作成している それでは今まで仕掛けたブレークポイントは解除して、別スレッドを生成する API, CreateThread ( Address 0041C0E8 KERNEL32.CreateThread )に「検索→現在のモジュール名」からブレークポイントを 仕掛けて実行してみましょう。(すでに用意されているスレッドを動かす ResumeThread も重要な API ですが今回はパス) ブレークしましたか?それでは CreateThread の引数を見てみましょう。 画面右下のスタックウィンドウをみて下さい。 (アドレスはOS環境により異なりますので、置き換えて読んで下さい。) 0065F7B0 00000000 |pSecurity = NULL 0065F7B4 00000000 |StackSize = 0 0065F7B8 00405262 |ThreadFunction = CRACKME.00405262 <---- 別スレッドとして起動するルーチンの先頭アドレス 0065F7BC 00990570 |pThreadParm = 00990570 0065F7C0 00000004 |CreationFlags = CREATE_SUSPENDED 0065F7C4 0099061C \pThreadId = 0099061C 重要なのは ThreadFunction です。別スレッドとして起動するルーチンの先頭アドレスが格納されています。 Ctrl + G から 00405262 と入力して該当ルーチンに飛んでブレークポイントを仕掛けましょう。 F9 を押すとブレークしました。ちょっと長そうなルーチンなので、とりあえず F8 連打で適当に進めて みると、アドレス 004052BE で停止してしまいます。 004052BE . FF56 48 call near dword ptr ds:[esi+48] ; CRACKME.00412874 どうやらスレッドの優先順位を変えているようです。右側に表示されているアドレス 00412874 に F7 キーでジャンプしてみましょう。 00412874 . B8 D0 B8 41 00 ascii "クミクA",0 ; もしブレークポイントを仕掛けるなら 00412879 . E8 2627FFFF call CRACKME.00404FA4 ; ここに仕掛けると警告は出ない ここの命令も長いですが、適当に F8 連打すると、アドレス 0041293E で途中で止まってしまいました。 0041293B . 6A FF push -1 ; /Timeout = INFINITE 0041293D . 53 push ebx ; |hObject 0041293E . FF15 68C14100 call near dword ptr ds:[<&KERNEL32.WaitForSingleObject>] ; \WaitForSingleObject この命令は最初の方でも出てきましたが、次のいずれかが成立すると制御を返す命令です。 ・指定されたオブジェクトがシグナル状態になった。 ・タイムアウト時間が経過した。 タイムアウト時間については、INFINITE が指定されているため、オブジェクトがシグナル状態になるまで待機し続けます。 とりあえずこれまでのブレークポイントをすべて解除して、FindCloseChangeNotificationの後の命令アドレス、 0041294B にブレークポイントを仕掛けて再実行した方が良さそうです。 解析では基本的にトライ&エラーの繰り返しです。根気強くいきましょう。 0041294B . 8B46 50 mov eax, dword ptr ds:[esi+50] ; CRACKME.00401D70 0041294E . 85C0 test eax, eax 00412950 . 74 08 je short CRACKME.0041295A 00412952 . FF76 4C push dword ptr ds:[esi+4C] ; [esi+4C] = 入力パスの先頭アドレス 00412955 . FFD0 call eax ; call 00401D70 と同じ さて、五月蠅い API 群を抜けるとこのような比較分岐に当たりました。ここで eax レジスタには 00401D70 が 格納されていますが、この値は先ほども出てきました。 00401F24 |. 68 701D4000 push CRACKME.00401D70 ; ←ここ 00401F29 |. C74424 28 D059>mov dword ptr ss:[esp+28], CRACKME.004259D0 ; ASCII "CR" 00401F31 |. 894424 2C mov dword ptr ss:[esp+2C], eax 00401F35 |. E8 7D0A0100 call CRACKME.004129B7 <-- おそらくこの call で別スレッドを作成している アドレス 00401D70, 非常に怪しいですね。 次の call eax でジャンプしていますが、直前の push 命令で入力パスの先頭アドレスをプッシュしています。 これは気づきにくいかもしれませんが、このあたりは経験に基づくカンのようなものに頼らなければなりません。 理論的に説明が出来なくて申し訳ありません。 とにかく、キーチェック部分を別スレッドで処理するだけでこんなに難しくなるのですね。 *OllyDbg は Win2000 環境で真価を発揮するわけですが、Win2000 環境なら メモリブレークポイントが使えるので入力パスのアドレスに仕掛ければ直ぐさまパスチェッカ内部でブレークすると思うのですが、 Win2000 では未確認です。各自確認してみてください。ちなみにこの方法はcrackme #20 で有効な手段です。 さて、アドレス 00401D70 に行ったところでいよいよ入力パス比較部分に入りました。 このチェッカは非常にややこしい上、トレースしてもレジスタやメモリに正解パスが見えないので、 かなり難しいと思います。とにかく行ってみましょう。 00401D7A . 8B79 04 mov edi, dword ptr ds:[ecx+4] ; edi = 固有 ID 00401D7D . 8B01 mov eax, dword ptr ds:[ecx] ; パスの先頭アドレス ここで固有 ID が出てきています。この値は頻繁に参照されるので要注意。 固有 ID 表示が 11-5599CC であれば、 edi = 115599CC となります。 次に、00401D87 - 00401D9A のループで、入力パスが "CR0123-4567-89AB" であれば、 esi = 0123 となるはずです。ここから先はとてもややこしい処理となっているため、 逆汗リストの右側にコメントを入れてます。参照して下さい。 00401D70 . 83EC 0C sub esp, 0C 00401D73 . 8B4C24 10 mov ecx, dword ptr ss:[esp+10] 00401D77 . 53 push ebx 00401D78 . 56 push esi 00401D79 . 57 push edi 00401D7A . 8B79 04 mov edi, dword ptr ds:[ecx+4] ; edi = 固有 ID 00401D7D . 8B01 mov eax, dword ptr ds:[ecx] ; パスの先頭アドレス 00401D7F . 33F6 xor esi, esi 00401D81 . 897C24 1C mov dword ptr ss:[esp+1C], edi 00401D85 . 33C9 xor ecx, ecx 00401D87 > 8A5408 02 mov dl, byte ptr ds:[eax+ecx+2] ; パスの3文字目から1文字ずつ 00401D8B . 81E2 FF000000 and edx, 0FF 00401D91 . C1E6 04 shl esi, 4 00401D94 . 03F2 add esi, edx 00401D96 . 41 inc ecx 00401D97 . 83F9 04 cmp ecx, 4 ; "CR0123-4567-89AB" なら 00401D9A .^7C EB jl short CRACKME.00401D87 ; esi = 0123 となる 00401D9C . 8A48 07 mov cl, byte ptr ds:[eax+7] ; 8 文字目 00401D9F . 8A50 08 mov dl, byte ptr ds:[eax+8] ; 9 文字目 00401DA2 . C0E1 04 shl cl, 4 ; "CR0123-4567-89AB" なら 00401DA5 . 02CA add cl, dl ; cl = 45 となる 00401DA7 . 8A58 0A mov bl, byte ptr ds:[eax+A] ; 11 文字目 00401DAA . 8BD7 mov edx, edi ; edx = 115599CC(固有ID) 00401DAC . C1EA 18 shr edx, 18 ; edx = 00000011(固有IDの一部) 00401DAF . 32CA xor cl, dl ; cl = 11 xor 45 が行われる ----+ 00401DB1 . 8A50 09 mov dl, byte ptr ds:[eax+9] ; 10 文字目 | 00401DB4 . C0E2 04 shl dl, 4 ; 10文字目 + 11文字目 | 00401DB7 . 02D3 add dl, bl ; dl = 67 | 00401DB9 . 8BDF mov ebx, edi ; ebx = 115599CC(固有ID) | 00401DBB . C1EB 10 shr ebx, 10 ; ebx = 00001155 | 00401DBE . 32D3 xor dl, bl ; dl = 67 xor 55(固有IDの一部) | 00401DC0 . 8A58 0D mov bl, byte ptr ds:[eax+D] ; パス 14 文字目 | 00401DC3 . 885424 10 mov byte ptr ss:[esp+10], dl ; 67 xor 55 の結果を格納 | 00401DC7 . 8A50 0C mov dl, byte ptr ds:[eax+C] ; パス 13 文字目 | 00401DCA . C0E2 04 shl dl, 4 ; bl = 9, dl = 80 になる | 00401DCD . 02D3 add dl, bl ; "CR0123-4567-89AB"ならdl = 89 | 00401DCF . 8B5C24 1C mov ebx, dword ptr ss:[esp+1C] ; ebx = 115599CC(固有ID) | 00401DD3 . C1EB 08 shr ebx, 8 ; ebx = 00115599 | 00401DD6 . 32D3 xor dl, bl ; dl = 89 xor 99 -------------+ | 00401DD8 . 8A58 0E mov bl, byte ptr ds:[eax+E] ; パス 15 文字目 | | 00401DDB . C0E3 04 shl bl, 4 ; "CR0123-4567-89AB" なら | | 00401DDE . 0258 0F add bl, byte ptr ds:[eax+F] ; パス 16 文字目, bl = AB | | 00401DE1 . 8A4424 1C mov al, byte ptr ss:[esp+1C] ; 固有ID の下2桁 "CC" | | 00401DE5 . 884C24 14 mov byte ptr ss:[esp+14], cl ; cl <-------------------------+ 00401DE9 . 885424 0C mov byte ptr ss:[esp+C], dl ; dl <-----------------------+ 00401DED . 32D8 xor bl, al ; 15,16文字目 xor CC(固有IDの一部) -+ 00401DEF . 3ACA cmp cl, dl ; 比較部分、等しいとダメ | 00401DF1 . 885C24 1C mov byte ptr ss:[esp+1C], bl ; <-格納----------------------------+ 00401DF5 . 74 64 je short CRACKME.00401E5B 00401DF7 . 3ACB cmp cl, bl ; 00401DF9 . 74 60 je short CRACKME.00401E5B 00401DFB . 8B5424 0C mov edx, dword ptr ss:[esp+C] ; 00401DFF . 8B4C24 1C mov ecx, dword ptr ss:[esp+1C] ; ecx = 115599CC(固有ID) 00401E03 . 81E2 FF000000 and edx, 0FF ; 余計な部分を排除 00401E09 . 81E1 FF000000 and ecx, 0FF ; 同様に余計な部分を排除 00401E0F . 8BC2 mov eax, edx 00401E11 . 0FAFC2 imul eax, edx 00401E14 . 0FAFC2 imul eax, edx ; eax = edx * edx * edx 00401E17 . 8BD1 mov edx, ecx 00401E19 . 0FAFD1 imul edx, ecx 00401E1C . 0FAFD1 imul edx, ecx ; edx = ecx * ecx * ecx 00401E1F . 03C2 add eax, edx ; ここで3乗したもの同士足してる 00401E21 . 8B5424 14 mov edx, dword ptr ss:[esp+14] ; edx に 8,9 文字目 xor 固有ID 00xxxxxx が入る 00401E25 . 81E2 FF000000 and edx, 0FF ; 00401E2B . 8B4C24 10 mov ecx, dword ptr ss:[esp+10] ; ecx に 10,11 文字目 xor 固有 ID xx00xxxx が入る 00401E2F . 8BFA mov edi, edx ; edx に 8,9 文字目 xor 固有ID 00xxxxxx が入る 00401E31 . 81E1 FF000000 and ecx, 0FF 00401E37 . 0FAFFA imul edi, edx 00401E3A . 0FAFFA imul edi, edx ; edi = edx * edx * edx 00401E3D . 8BD1 mov edx, ecx 00401E3F . 0FAFD1 imul edx, ecx 00401E42 . 0FAFD1 imul edx, ecx ; edx = ecx * ecx * ecx 00401E45 . 03FA add edi, edx ; 3乗したもの同士足してる 00401E47 . 3BF8 cmp edi, eax ; 上の3乗、直前の3乗を比較 00401E49 . 75 10 jnz short CRACKME.00401E5B ; 等しいかな? 00401E4B . 33C9 xor ecx, ecx ; "CR0123-4567-89AB" なら 00401E4D . 3BF0 cmp esi, eax ; esi = 0123, これと eax を比較。 00401E4F . 0F94C1 sete cl ; 等しければ cl = 1 となる 00401E52 . 5F pop edi 00401E53 . 5E pop esi 00401E54 . 8BC1 mov eax, ecx ; cl の値を eax (戻り値)に移している 00401E56 . 5B pop ebx 00401E57 . 83C4 0C add esp, 0C 00401E5A . C3 retn 00401E5B > 5F pop edi 00401E5C . 5E pop esi 00401E5D . 33C0 xor eax, eax ; 戻り値 0 00401E5F . 5B pop ebx 00401E60 . 83C4 0C add esp, 0C 00401E63 . C3 retn これにより、トレースから得た情報をまとめてみると、 ・正当パスの形式は CRxxxx-yyyy-zzzz で、xxxx, yyyy, zzzz は [0-9][A-F] の 範囲内となります([a-f] は範囲外)。 ID を 115599CC, パスを CR0123-4567-89AB とすると、 45h xor 11h = 54h (ア) <--+--+ (00401D9A - 00401DAF) ( 8, 9 文字目結合) (固有ID XXxxxxxx) | | 67h xor 55h = 32h (イ) ≠ | (00401DA7 - 00401DBE) (10,11 文字目結合) (固有ID xxXXxxxx) | ≠ 89h xor 99h = 10h (ウ) <--+ | (00401DC0 - 00401DD6) (13,14 文字目結合) (固有ID xxxxXXxx) | ABh xor CCh = 67h (エ) <-----+ (00401DD8 - 00401DED) (15,16 文字目結合) (固有ID xxxxxxXX) 上記のような処理を行います。 さらに各値には次のような条件が定められています。 xxxx ≧ FFFFh(65535) ..(1) (00401D94, 4 桁しかないため最大値は FFFFh となる) (ア) ≠ (ウ) かつ (ア) ≠ (エ) ..(2) (00401DF1 - 00401DF9) (ア)^3+(イ)^3 = (ウ)^3+(エ)^3 ..(3) (00401DFB - 00401E1F, 00401E21 - 00401E49) (ウ)^3+(エ)^3 = xxxx ..(4) (00401E4D) 条件(3)(4)より (ア)^3+(イ)^3 = xxxx ..(4') これらの条件を満たす (ア)(イ)(ウ)(エ) の値を導けばよいことがわかります。 ここで条件(1)(4)(4')について、 40^3 = FA00h(64000), 41^3 = 10D39h(68921) であることを考えると、 (ア) (イ) (ウ) (エ) は 40 以下の値でなければならないことがわかります。 a^3+b^3 ≦ FFFFh (a≦40 、b≦40)を満たす a、b を総当たりで求めると、 06c1 = 01^3 + 0c^3 = 09^3 + 0a^3 1008 = 02^3 + 10^3 = 09^3 + 0f^3 3608 = 02^3 + 18^3 = 12^3 + 14^3 50cb = 0a^3 + 1b^3 = 13^3 + 18^3 8040 = 04^3 + 20^3 = 12^3 + 1e^3 9990 = 02^3 + 22^3 = 0f^3 + 21^3 9c61 = 09^3 + 22^3 = 10^3 + 21^3 (ここではアルファベットは小文字になっていますが、 b65b = 03^3 + 24^3 = 1b^3 + 1e^3 大文字に直さないと不正解になります。注意!) fae8 = 11^3 + 27^3 = 1a^3 + 24^3 9通りの解があることがわかりました。 今回は単にパス出しを行いたいので、9通りの解の中のひとつ06c1 = 01^3 + 0c^3 = 09^3 + 0a^3 に 着目して正解パスを作り出しましょう。ここでは固有ID は 11-5599CC として考えます。 まず第一段階として、"CR06C1-yyyy-zzzz" 、 固有ID から値を拾って2桁ずつに区切ります。 115599CC → 11 55 99 CC 続いて、解となる式から値を拾い、同様に2桁ずつに区切ります。 06c1 = 01^3 + 0c^3 = 09^3 + 0a^3 → 01 0C 09 0A ~~ ~~ ~~ ~~ そして、先ほど区切った値同士を xor して値を出します。実は実際のルーチンでは2桁ずつ 区切って個別にチェックしていますが、ここでは別に2桁ずつに区切らなくても OK です。 115599CC xor 010C090A = 105990C6 これより 105990C6 が出たので、出た値を4桁で区切り、1059-90C6 とします。 以上を合わせると、固有ID = 115599CC に対応した正解パスは "CR06C1-1059-90C6" となります。 これは一例なのでまだ正解パスのパターンはあります。 キージェネを作成したいのであれば、全てのパターンについて理解する必要があります。 以上にて NORMAL の解説は終了になります。